/* enlace.c
 * Luiz Guilherme M. Massa, 10005312
 * Matheus Martins Alves, 10108835
 * Marcelo C. R. Lopes Junior, 10104156
 * Vitor Sousa Negro, 10068104
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define NR_MAX_NOS 10
#define NR_MAX_CONEXOES 10
#define NR_MAX_THREADS 5
#define TESTE_SINC 1

struct struct_no {
    char ip[30];
    int porta;
    int num_conexoes;
    int conexao[NR_MAX_CONEXOES];
    int conexao_mtu[NR_MAX_CONEXOES];
};

typedef struct{
    char mensagem[1000000];
    int no_dst;
    int no_src;
    int preenchido;
} buffer_app_transp;




typedef struct {
    char data[512];
    int tamanho_msg;
    char tipo;
    int ack;
    int no_src;
    int no_dst;
} segmento;

typedef struct {
    char tipo;
    int no_src;
    int no_dst;
    int flagFragmentos; 
    int offset;
    int identificacaoPacote;
    int tamanho_data;
    int checksum_segmento;
    segmento segmento;
} pacote;

typedef struct {
    pacote pacote;
    char ip_dst[30];
    int porta_dst;
    int checksum;
} quadro;

// Buffers

typedef struct {
    int preenchido;
    int no_dst;
    int tamanho_data;
    pacote pacote;
} buffer_rede_enlace;

typedef struct {
    int preenchido;
    pacote pacote;
    int MTU;
} buffer_enlace_rede;

typedef struct {
    int preenchido;
    int no_dst;
    segmento segmento;
} buffer_transp_rede;

typedef struct {
    int preenchido;
    segmento segmento;
} buffer_rede_transp;


typedef struct {
    int numero_nos;
    int numero_no[NR_MAX_NOS];
    int is_vizinho[NR_MAX_NOS];
} info_tabela_rede;

typedef struct {
    pacote pacote;
    int MTU;
} buffer_enlace_rede_erro;


buffer_enlace_rede bufferEnlaceRede;
buffer_rede_enlace bufferRedeEnlace;
buffer_rede_enlace bufferRedeEnlace2;
buffer_transp_rede bufferTransporteRede;
buffer_rede_transp bufferRedeTransporte;
info_tabela_rede bufferTabelaRede;
buffer_enlace_rede_erro erro_mtu;
buffer_app_transp bufferAplicacaoTransporte;

// Rede Virtual
struct struct_no nos[NR_MAX_CONEXOES];

int qtdeNos;
long meuIndiceNo;

pthread_t thread_id[NR_MAX_THREADS];
pthread_mutex_t sinc_recebe_enlace_rede, sinc_envia_enlace_rede;
pthread_mutex_t sinc_recebe_rede_transp, sinc_envia_rede_transp;
pthread_mutex_t sinc_envia_enlace_rede2;
pthread_mutex_t sinc_erro_mtu,sinc_erro_mtu2,sinc_envia_gbn,sinc_gbn_sender,sinc_envia_ack;


void* recebeQuadro(void);
void* enviaQuadro(void);
void* enviaQuadro2(void);



int inArray(int numero, int vetor[], int qtde_posicoes)
{
    int i;
    for(i=0; i<qtde_posicoes; i++)
        if(vetor[i] == numero)
            return 1;
    return 0;
}

void enviaQuemSaoVizinhos()
{
    int i;
    for(i=0; i<qtdeNos; i++) {
        bufferTabelaRede.numero_nos++;
        bufferTabelaRede.numero_no[i] = i;
        bufferTabelaRede.is_vizinho[i] = inArray(i, nos[meuIndiceNo].conexao, nos[meuIndiceNo].num_conexoes);
    }
}

void printNo(int num) {
    int i = 0;
    if(TESTE_SINC) printf("NUM: %d\nIP: %s\nPorta: %d\n", num, nos[num].ip, nos[num].porta);
    while(i < nos[num].num_conexoes) {
        if(TESTE_SINC) printf("Link com %d (%s:%d) - MTU: %d\n", nos[num].conexao[i], nos[(nos[num].conexao[i])].ip, nos[(nos[num].conexao[i])].porta, nos[num].conexao_mtu[i]);
        i++;
    }
}

int setRedeVirtual(char nome_arquivo[]) {
    FILE *arquivo;
    char nova_string[50];
    int num1, num2, mtu; // Variáveis para leitura do enlace

    arquivo = fopen(nome_arquivo, "r"); //Inicia o ponteiro no começo do arquivo
    
    qtdeNos = 0;

    if(arquivo == NULL)
        return -1;

    fscanf(arquivo, "%s", nova_string);  // [Nós]

    while(!feof(arquivo)) {
        fscanf(arquivo, "%s", nova_string);
        if(atoi(nova_string) == qtdeNos+1) {
            fscanf(arquivo, " , %s", nos[qtdeNos].ip);
            fscanf(arquivo, " : %d", &nos[qtdeNos].porta);
            nos[qtdeNos].num_conexoes = 0;
            qtdeNos++;
        }
        else
            break;
    }

    while(!feof(arquivo)) {
        fscanf(arquivo, "%d", &num1);
        fscanf(arquivo, " > %d", &num2);
        fscanf(arquivo, " : %d", &mtu);
        
        num1--;
        num2--;

        nos[num1].conexao[nos[num1].num_conexoes] = num2;
        nos[num2].conexao[nos[num2].num_conexoes] = num1;
        nos[num1].conexao_mtu[nos[num1].num_conexoes] = mtu;
        nos[num2].conexao_mtu[nos[num2].num_conexoes] = mtu;
        nos[num1].num_conexoes++;
        nos[num2].num_conexoes++;
    }
    return 1;
}

int issetNo(int num) {
    if(num >= 0 && num < qtdeNos)
        return 1;
    return 0;
}

void getIpPorta(int noDestino, char ipDestino[], int *portaDestino)
{
    strcpy(ipDestino, nos[noDestino].ip);
    *portaDestino = nos[noDestino].porta;
}

int getChecksum(quadro quadro)
{
    int soma = 0;
    char buffer[sizeof(quadro.pacote)];

    memcpy(&buffer, &quadro.pacote, sizeof(quadro.pacote));
    soma += getHashChar(buffer, sizeof(buffer));
    soma += getHashChar(quadro.ip_dst, strlen(quadro.ip_dst));
    soma += quadro.porta_dst;
    return soma;
}



int getHashChar(char datagrama[], int tamanho) {
    int soma = 0, i = 0;

    while(i < tamanho)
        soma = (soma + (int) datagrama[i++]) % 1474836476; // % -> Não estourar int
    return soma;
}

int getNumeroNo(char ip[], int porta) {
    int i = 0;

    while(i < qtdeNos) {
        if(strcmp(ip, nos[i].ip) == 0 && porta == nos[i].porta)
            return i;
        i++;
    }
    return -1;
}

int getMTU(int meuIndiceNo, int indiceDestino) {
    int i = 0;

    while(i < nos[meuIndiceNo].num_conexoes) {
        if(nos[meuIndiceNo].conexao[i] == indiceDestino)
            return nos[meuIndiceNo].conexao_mtu[i];
        i++;
    }
    return -1;
}




void iniciaEnlace(char arquivo[], int meuNo) {
    // Iniciar enlace - 1) Setar rede virtual
    if(!setRedeVirtual(arquivo)) {
        perror("setRedeVirtual()");
        exit(1);
    }

    // Iniciar enlace - 2) Setar nó
    if(!issetNo(meuIndiceNo = meuNo)) {
        perror("setNo()");
        exit(1);
    }

    if(TESTE_SINC) printNo(meuIndiceNo);
    enviaQuemSaoVizinhos();

    // Iniciar enlace - 3) Criação dos mutex
    if(pthread_mutex_init(&sinc_recebe_enlace_rede, NULL) != 0) {
        perror("pthread_mutex_init()");
        exit(1);
    }

    if(pthread_mutex_init(&sinc_envia_enlace_rede, NULL) != 0) {
        perror("pthread_mutex_init()");
        exit(1);
    }

    if(pthread_mutex_init(&sinc_recebe_rede_transp, NULL) != 0) {
        perror("pthread_mutex_init()");
        exit(1);
    }

    if(pthread_mutex_init(&sinc_envia_rede_transp, NULL) != 0) {
        perror("pthread_mutex_init()");
        exit(1);
    }

    if(pthread_mutex_init(&sinc_envia_enlace_rede2, NULL) != 0) {
        perror("pthread_mutex_init()");
        exit(1);
    }
    
    if(pthread_mutex_init(&sinc_erro_mtu, NULL) != 0) {
        perror("pthread_mutex_init()");
        exit(1);
    }
    
    if(pthread_mutex_init(&sinc_erro_mtu2, NULL) != 0) {
        perror("pthread_mutex_init()");
        exit(1);
    }
    if(pthread_mutex_init(&sinc_gbn_sender, NULL) != 0) {
        perror("pthread_mutex_init()");
        exit(1);
    }
    if(pthread_mutex_init(&sinc_envia_gbn, NULL) != 0) {
        perror("pthread_mutex_init()");
        exit(1);
    }
    if(pthread_mutex_init(&sinc_envia_ack, NULL) != 0) {
        perror("pthread_mutex_init()");
        exit(1);
    }

    bufferRedeEnlace.preenchido = 0;
    bufferEnlaceRede.preenchido = 0;
    bufferRedeEnlace2.preenchido = 0;

    // Iniciar enlace - 4) Iniciar rotinas do enlace
    if(pthread_create(&(thread_id[0]), NULL, &recebeQuadro) != 0) {
        perror("pthread_create()");
        exit(1);
    }
   
     if(pthread_create(&(thread_id[1]), NULL, &enviaQuadro) != 0) {
        perror("pthread_create()");
        exit(1);
    }
   
     if(pthread_create(&(thread_id[1]), NULL, &enviaQuadro2) != 0) {
        perror("pthread_create()");
        exit(1);
    }

}

void fechaEnlace()
{
    // Aguarda as threads serem terminadas
    pthread_join(thread_id[0], NULL);
    pthread_join(thread_id[1], NULL);
    pthread_join(thread_id[2], NULL);

    // Exclui os mutex de exclusão mútua dos buffers
    // pthread_mutex_destroy(&exclusao_recebe);
    // pthread_mutex_destroy(&exclusao_envia);
}


//Rotina receber pacote
void* recebeQuadro(void)
{
    unsigned short port;
    struct sockaddr_in client;
    struct sockaddr_in server;
    int s;
    int ns;
    int namelen;
    int flag_add_buffer;
    int client_address_size;
    int tamanho_quadro;
    int quadro_corrompido = 0;
    quadro quadro_recebido;

    if((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("Socket()");
        exit(2);
    }

    /*
    * Define a qual endereço IP e porta o servidor estará ligado.
    * IP = INADDDR_ANY -> faz com que o servidor se ligue em todos
    * os endereços IP
    */
    server.sin_family = AF_INET;
    server.sin_port   = htons(nos[meuIndiceNo].porta);
    server.sin_addr.s_addr = INADDR_ANY;

    /*
    * Liga o servidor à porta definida anteriormente.
    */
    if(bind(s, (struct sockaddr *)&server, sizeof(server)) < 0) {
        perror("Bind()");
        exit(3);
    }

    namelen = sizeof(server);
    if(getsockname(s, (struct sockaddr *) &server, &namelen) < 0) {
        perror("getsockname()");
        exit(1);
    }

    client_address_size = sizeof(client);
            
    while(1)
    {
        if(quadro_corrompido == 0)
            pthread_mutex_lock(&sinc_recebe_enlace_rede);

        if(TESTE_SINC) printf("Enlace -> Aguardando quadro na porta %d.\n", nos[meuIndiceNo].porta);
        tamanho_quadro = recvfrom(s, &quadro_recebido, (sizeof(quadro_recebido)), 0, (struct sockaddr *) &client, &client_address_size);

        if(quadro_recebido.checksum == getChecksum(quadro_recebido)) { 
            quadro_corrompido = 0;
            // pthread_mutex_lock(&exclusao_recebe);
            memcpy(&bufferEnlaceRede.pacote, &quadro_recebido.pacote, sizeof(bufferEnlaceRede.pacote));
            bufferEnlaceRede.preenchido = 1;
            // pthread_mutex_unlock(&exclusao_recebe);

            if(TESTE_SINC) printf("Enlace -> Recebi um quadro, liberando sincronia recebe.\n");
            pthread_mutex_unlock(&sinc_recebe_enlace_rede);
            usleep(1);
	        
        }
        else { 
            quadro_corrompido = 1;
            if(TESTE_SINC) printf("Enlace -> Recebi um quadro corrompido.\n");
        }
    }

    close(s);
    return NULL;
}


void* enviaQuadro(void)
{
    int s;
    struct sockaddr_in server;
    quadro quadro_de_envio;
    int noDestino;
    char ipDestino[30];
    int portaDestino;
    int mtu_enviaquadro;

    while(1)
    {
        // Aguarda a camada de rede ser iniciada

        pthread_mutex_lock(&sinc_envia_enlace_rede);

        while(bufferRedeEnlace.preenchido != 1) {
            if(TESTE_SINC) printf("Enlace -> Não tem nada no buffer da rede, aguardando...\n");
            pthread_mutex_unlock(&sinc_envia_enlace_rede);
            usleep(1);
            pthread_mutex_lock(&sinc_envia_enlace_rede);
        }

        if(TESTE_SINC) printf("Enlace -> Buffer preenchido, vou enviar.\n");

        bufferRedeEnlace.preenchido = 0;

        memcpy(&quadro_de_envio.pacote, &bufferRedeEnlace.pacote, sizeof(bufferRedeEnlace.pacote));
        noDestino = bufferRedeEnlace.no_dst;

        mtu_enviaquadro = getMTU(meuIndiceNo, noDestino);
        
        // Se não coube no MTU...
        if(mtu_enviaquadro < bufferRedeEnlace.tamanho_data) {
            if(TESTE_SINC) printf("Enlace -> Não coube na MTU.\n");
            memcpy(&erro_mtu.pacote, &quadro_de_envio.pacote, sizeof(quadro_de_envio.pacote));
            erro_mtu.MTU = mtu_enviaquadro;
            pthread_mutex_unlock(&sinc_envia_enlace_rede);
            usleep(1);
            // perror("Link insuficiente :O.");
            // exit(1)
        } else { 
    		if(erro_mtu.MTU == 0){
                pthread_mutex_unlock(&sinc_erro_mtu);
                usleep(1);
            } 

            getIpPorta(noDestino, ipDestino, &portaDestino);

    		strcpy(quadro_de_envio.ip_dst, ipDestino);
    		quadro_de_envio.porta_dst = portaDestino;
    		quadro_de_envio.checksum = getChecksum(quadro_de_envio);

    		/* Cria um socket UDP (dgram) */
    		if((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
    		    perror("socket()");
    		    exit(1);
    		}

    		/* Define o endereo IP e a porta do servidor */
    		server.sin_family      = AF_INET;            /* Tipo do endereo         */
    		server.sin_port        = htons(quadro_de_envio.porta_dst); /* Porta do servidor */
    		server.sin_addr.s_addr = inet_addr(quadro_de_envio.ip_dst); /* Endereo IP do servidor  */

    		// if(sendto_garbled(s, &quadro_de_envio, (sizeof(quadro)), 0, (struct sockaddr *)&server, sizeof(server)) < 0) {
    		//     perror("sendto()");
    		//     exit(1);
    		// }

            sendto_garbled(s, &quadro_de_envio, (sizeof(quadro)), 0, (struct sockaddr *)&server, sizeof(server));
    		if(TESTE_SINC) printf("Enlace -> SendTo feito para %d, liberando sincronia envia...\n", portaDestino);
            usleep(50000);
                
    		pthread_mutex_unlock(&sinc_envia_enlace_rede);
    		usleep(1);

    		/* Fecha o socket */
    		close(s);
	      }
    }

    return NULL;
}


void* enviaQuadro2(void)
{
    
    int s;
    struct sockaddr_in server;

    quadro quadro_de_envio;
    int noDestino;
    char ipDestino[30];
    int portaDestino;
    int mtu_enviaquadro2;

    while(1)
    {
        // Aguarda a camada de rede ser iniciada
        pthread_mutex_lock(&sinc_envia_enlace_rede2);
        while(bufferRedeEnlace2.preenchido != 1) {
            if(TESTE_SINC) printf("Enlace2 -> Não tem nada no buffer da rede, aguardando...\n");
            sleep(1);
            pthread_mutex_unlock(&sinc_envia_enlace_rede2);
            usleep(1);
            pthread_mutex_lock(&sinc_envia_enlace_rede2);
        }

        if(TESTE_SINC) printf("Enlace2 -> Buffer preenchido, vou enviar.\n");

        bufferRedeEnlace2.preenchido = 0;

        memcpy(&quadro_de_envio.pacote, &bufferRedeEnlace2.pacote, sizeof(bufferRedeEnlace2.pacote));
        noDestino = bufferRedeEnlace2.no_dst;
        mtu_enviaquadro2 = getMTU(meuIndiceNo, noDestino); 

        if(mtu_enviaquadro2 < bufferRedeEnlace2.tamanho_data) {
            memcpy(&bufferEnlaceRede.pacote,&quadro_de_envio.pacote,sizeof(quadro_de_envio.pacote));
            bufferEnlaceRede.MTU = mtu_enviaquadro2;
            pthread_mutex_lock(&sinc_recebe_enlace_rede);
            pthread_mutex_unlock(&sinc_recebe_enlace_rede);
            usleep(1);
        }
       else {

            // if(erro_mtu.MTU == 0){
            //     pthread_mutex_unlock(&sinc_erro_mtu2);
            //     usleep(1);
            // } 

            getIpPorta(noDestino, ipDestino, &portaDestino);

            strcpy(quadro_de_envio.ip_dst, ipDestino);
            quadro_de_envio.porta_dst = portaDestino;
            quadro_de_envio.checksum = getChecksum(quadro_de_envio);

            /* Cria um socket UDP (dgram) */
            if((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
                perror("socket()");
                exit(1);
            }

            /* Define o endereo IP e a porta do servidor */
            server.sin_family      = AF_INET;            /* Tipo do endereo         */
            server.sin_port        = htons(quadro_de_envio.porta_dst); /* Porta do servidor */
            server.sin_addr.s_addr = inet_addr(quadro_de_envio.ip_dst); /* Endereo IP do servidor  */

            
            sendto_garbled(s, &quadro_de_envio, (sizeof(quadro)), 0, (struct sockaddr *)&server, sizeof(server));
            if(TESTE_SINC) printf("Enlace -> SendTo feito para %d, liberando sincronia envia...\n", portaDestino);    
            usleep(50000);
                    
            pthread_mutex_unlock(&sinc_envia_enlace_rede2);
            usleep(1);
            /* Fecha o socket */
            close(s);
        }
    }

    return NULL;
}
